﻿#region License
// (c) Intergen.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
#endregion

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using TextGlow.Control.Controls;
using TextGlow.Control.Model;
using TextGlow.Control.Utilities;
using TextGlow.Control.Controls.Document;
using TextGlow.Control.Packaging;
using System.Linq;
using System.Text;
using System.Windows.Media.Imaging;
using System.IO;
using TabControl = TextGlow.Control.Controls.Document.TabControl;

namespace TextGlow.Control.Controls
{
  public class DocumentBuilder
  {
    private static readonly Unit DefaultFontSize = Unit.Point(11);
    private static readonly Unit DefaultLeftRightCellMargin = Unit.Twip(108);
    private static readonly Color DefaultTextColor = Color.FromArgb(255, 0, 0, 0);
    private static readonly Color DefaultBorderColor = Color.FromArgb(255, 0, 0, 0);
    private static readonly Color DefaultBackgroundColor = Color.FromArgb(0, 0, 0, 0);

    private Model.Document _documentModel;
    private Dictionary<NumberingLevel, int> _listCounts = new Dictionary<NumberingLevel, int>();
    private List<PageControl> _pages;
    private bool _pageEnded;
    private int _sectionPageCount;
    private Action<PageControl> _addPageToParent;

    public DocumentBuilder(Model.Document documentModel, Action<PageControl> addPageToParent)
    {
      if (documentModel == null)
        throw new ArgumentNullException("documentModel");
      if (addPageToParent == null)
        throw new ArgumentNullException("addPageToParent");

      _documentModel = documentModel;
      _addPageToParent = addPageToParent;
    }

    public List<PageControl> BuildDocument()
    {
      _pages = new List<PageControl>();

      foreach (Section section in _documentModel.Sections)
      {
        BuildSection(section);
      }

      return _pages;
    }

    private int GetNumberCount(int numberId, int level, bool increment)
    {
      NumberingDefinition numberingDefinition = _documentModel.NumberingDefinitions[numberId];
      NumberingLevel numberingLevel = numberingDefinition.NumberingLevels[level];

      bool start = !_listCounts.ContainsKey(numberingLevel);

      if (start)
        _listCounts[numberingLevel] = numberingLevel.Start.Value;

      int count = _listCounts[numberingLevel];

      if (increment && !start)
      {
        count++;
        _listCounts[numberingLevel] = count;
      }

      return count;
    }

    #region Page
    private PageControl CreatePageControl(Section section, IEnumerable<UIElement> children)
    {
      PageControl page = new PageControl();
      //page.BeginUpdate();

      page.Width = section.Properties.PageWidth.Value.ToPixels();
      page.Height = section.Properties.PageHeight.Value.ToPixels();

      page.MarginBottom = section.Properties.MarginBottom.Value.ToPixels();
      page.MarginFooter = section.Properties.MarginFooter.Value.ToPixels();
      page.MarginGutter = section.Properties.MarginGutter.Value.ToPixels();
      page.MarginHeader = section.Properties.MarginHeader.Value.ToPixels();
      page.MarginLeft = section.Properties.MarginLeft.Value.ToPixels();
      page.MarginRight = section.Properties.MarginRight.Value.ToPixels();
      page.MarginTop = section.Properties.MarginTop.Value.ToPixels();

      Header header;
      Footer footer;
      if (_sectionPageCount == 1 && (section.Properties.TitlePage ?? false))
      {
        header = section.Properties.HeaderFirst ?? section.Properties.HeaderDefault;
        footer = section.Properties.FooterFirst ?? section.Properties.FooterDefault;
      }
      else if (_sectionPageCount % 2 == 0 && _documentModel.EvenAndOddHeaders)
      {
        header = section.Properties.HeaderEven ?? section.Properties.HeaderDefault;
        footer = section.Properties.FooterEven ?? section.Properties.FooterDefault;
      }
      else
      {
        header = section.Properties.HeaderDefault;
        footer = section.Properties.FooterDefault;
      }

      if (header != null)
        page.HeaderContent.AddRange(CreatePageContent(header.Content).Select(e => (UIElement)e));
      if (footer != null)
        page.FooterContent.AddRange(CreatePageContent(footer.Content).Select(e => (UIElement)e));

      foreach (FrameworkElement pageElement in children)
      {
        page.PageContentChildren.Add(pageElement);
      }

      //page.Arrange(LayoutUtils.CreateRect(LayoutUtils.Zero, new Size(double.PositiveInfinity, double.PositiveInfinity)));

      return page;
    }

    private void BuildSection(Section section)
    {
      _sectionPageCount = 0;
      List<DocumentContent> sectionContent = section.Content;

      // create pages until there is no more section content remaining
      while (!CollectionUtils.IsEmpty(sectionContent))
      {
        List<DocumentContent> remainderSectionContent;
        List<UIElement> createdSectionElements = CreatePageContent(sectionContent, out remainderSectionContent);

        _sectionPageCount++;
        PageControl page = CreatePageControl(section, createdSectionElements);

        _pages.Add(page);
        _addPageToParent(page);

        List<UIElement> remainderContent = new List<UIElement>();
        remainderContent.AddRange(page.RemainderChildren);
        page.RemainderChildren.Clear();

        while (remainderContent.Count > 0)
        {
          _sectionPageCount++;
          page = CreatePageControl(section, remainderContent);

          _pages.Add(page);
          _addPageToParent(page);

          remainderContent = new List<UIElement>();
          remainderContent.AddRange(page.RemainderChildren);
          page.RemainderChildren.Clear();
        }

        sectionContent = remainderSectionContent;
      }
    }

    private List<UIElement> CreatePageContent(List<DocumentContent> blockContent)
    {
      List<DocumentContent> remainderContent;
      return CreatePageContent(blockContent, out remainderContent);
    }

    private List<UIElement> CreatePageContent(List<DocumentContent> blockContent, out List<DocumentContent> remainderContent)
    {
      _pageEnded = false;
      remainderContent = null;

      List<UIElement> contentControls = new List<UIElement>();

      for (int i = 0; i < blockContent.Count; i++)
      {
        Content content = blockContent[i];

        //Canvas c = new Canvas();
        //c.Width = 10;
        //c.Height = 10;
        //c.Background = new SolidColorBrush(Color.FromArgb(128, 0, 0, 0));

        //contentControls.Add(c);

        if (content is Paragraph)
        {
          Paragraph current = (Paragraph)content;
          Paragraph previous = ((i > 0) ? blockContent[i - 1] : null) as Paragraph;
          Paragraph next = ((i + 1 < blockContent.Count) ? blockContent[i + 1] : null) as Paragraph;

          ParagraphControl paragraphControl = BuildParagraph(current, previous, next);
          contentControls.Add(paragraphControl);
        }
        else if (content is Table)
        {
          contentControls.Add(BuildTable((Table)content));
        }
        else if (content is BlockStructuredTag)
        {
          BlockStructuredTag blockStructuredTag = (BlockStructuredTag)content;

          contentControls.AddRange(CreatePageContent(blockStructuredTag.Content));
        }

        if (_pageEnded)
        {
          remainderContent = CollectionUtils.Slice(blockContent, i, null).ToList();
          break;
        }
      }

      return contentControls;
    }
    #endregion

    #region Table
    private TableControl BuildTable(Table table)
    {
      TableControl tableControl = new TableControl();

      if (table.Parent == table.MainDocument)
        tableControl.IndentLeft = -(table.Rows[0].Cells[0].Properties.CellMargins.Left ?? DefaultLeftRightCellMargin).ToPixels();

      for (int rowIndex = 0; rowIndex < table.Rows.Count; rowIndex++)
      {
        TableRow row = table.Rows[rowIndex];

        TableRowControl rowControl = new TableRowControl();
        tableControl.Rows.Add(rowControl);

        for (int columnIndex = 0; columnIndex < row.Cells.Count; columnIndex++)
        {
          TableCell cell = row.Cells[columnIndex];

          Position verticalPosition = table.Rows.Position(row);
          Position horizontalPosition = row.Cells.Position(cell);

          TableCellControl cellControl = new TableCellControl();
          rowControl.Cells.Add(cellControl);

          cellControl.ColumnSpan = cell.Properties.GridSpan ?? 1;

          if (cell.Properties.VerticalMergeType == CellMergeType.Restart)
            cellControl.RowSpan = CalculateCellRowSpan(rowIndex, columnIndex, table);
          else if (cell.Properties.VerticalMergeType == CellMergeType.Continue)
            cellControl.RowSpan = 0;
          else
            cellControl.RowSpan = 1;

          cellControl.VerticalAlignment = FromVerticalAlignment(cell.Properties.VerticalAlignment ?? TextGlow.Control.Model.VerticalAlignment.Top);
          cellControl.Width = cell.Properties.PreferredWidth.Value.ToPixels();

          cellControl.BorderTop = FromDocumentBorder(cellControl, ResolveBorder(table, cell, Side.Top, verticalPosition, horizontalPosition));
          cellControl.BorderBottom = FromDocumentBorder(cellControl, ResolveBorder(table, cell, Side.Bottom, verticalPosition, horizontalPosition));
          cellControl.BorderLeft = FromDocumentBorder(cellControl, ResolveBorder(table, cell, Side.Left, verticalPosition, horizontalPosition));
          cellControl.BorderRight = FromDocumentBorder(cellControl, ResolveBorder(table, cell, Side.Right, verticalPosition, horizontalPosition));

          cellControl.MarginTop = Unit.ToPixels(cell.Properties.CellMargins.Top) ?? 0;
          cellControl.MarginRight = Unit.ToPixels(cell.Properties.CellMargins.Right) ?? DefaultLeftRightCellMargin.ToPixels();
          cellControl.MarginBottom = Unit.ToPixels(cell.Properties.CellMargins.Bottom) ?? 0;
          cellControl.MarginLeft = Unit.ToPixels(cell.Properties.CellMargins.Left) ?? DefaultLeftRightCellMargin.ToPixels();

          cellControl.Background = FromShading(cell.Properties.Shading);

          List<DocumentContent> cellContent;

          // trim final empty paragraph if cell has content
          if (cell.Content.Count >= 2
            && cell.Content.Last() is Paragraph
            && ((Paragraph)cell.Content.Last()).Content.IsEmpty())
            cellContent = cell.Content.Slice(0, -1).ToList();
          else
            cellContent = cell.Content;

          cellControl.CellContent.AddRange(CreatePageContent(cellContent).Select(e => (UIElement)e));
        }
      }

      return tableControl;
    }

    private int CalculateCellRowSpan(int rowIndex, int columnIndex, Table table)
    {
      int rowSpan = 1;

      for (int i = rowIndex + 1; i < table.Rows.Count; i++)
      {
        TableRow row = table.Rows[i];
        if (row.Cells[columnIndex].Properties.VerticalMergeType == CellMergeType.Continue)
          rowSpan++;
        else
          break;
      }

      return rowSpan;
    }

    public enum Side
    {
      Top,
      Bottom,
      Left,
      Right
    }

    private TextGlow.Control.Model.Border ResolveBorder(Table table, TableCell cell, Side side, Position verticalPosition, Position horizontalPosition)
    {
      TextGlow.Control.Model.Border tableBorder;
      TextGlow.Control.Model.Border insideBorder;
      TextGlow.Control.Model.Border cellBorder;
      bool outerEdge;

      switch (side)
      {
        case Side.Top:
          tableBorder = table.Properties.BorderTop;
          insideBorder = (cell.Properties.BorderInsideHorizontal.IsDefined) ? cell.Properties.BorderInsideHorizontal : table.Properties.BorderInsideHorizontal;
          cellBorder = cell.Properties.BorderTop;
          outerEdge = ((verticalPosition & Position.First) == Position.First);
          break;
        case Side.Bottom:
          tableBorder = table.Properties.BorderBottom;
          insideBorder = (cell.Properties.BorderInsideHorizontal.IsDefined) ? cell.Properties.BorderInsideHorizontal : table.Properties.BorderInsideHorizontal;
          cellBorder = cell.Properties.BorderBottom;
          outerEdge = ((verticalPosition & Position.Last) == Position.Last);
          break;
        case Side.Left:
          tableBorder = table.Properties.BorderLeft;
          insideBorder = (cell.Properties.BorderInsideVertical.IsDefined) ? cell.Properties.BorderInsideVertical : table.Properties.BorderInsideVertical;
          cellBorder = cell.Properties.BorderLeft;
          outerEdge = ((horizontalPosition & Position.First) == Position.First);
          break;
        case Side.Right:
          tableBorder = table.Properties.BorderRight;
          insideBorder = (cell.Properties.BorderInsideVertical.IsDefined) ? cell.Properties.BorderInsideVertical : table.Properties.BorderInsideVertical;
          cellBorder = cell.Properties.BorderRight;
          outerEdge = ((horizontalPosition & Position.Last) == Position.Last);
          break;
        default:
          throw new ArgumentOutOfRangeException("side");
      }

      if (cellBorder.IsDefined)
        return cellBorder;
      else
        return (outerEdge) ? tableBorder : insideBorder;
    }
    #endregion

    #region Paragraph
    private ParagraphControl BuildParagraph(Paragraph paragraph, Paragraph previous, Paragraph next)
    {
      ParagraphControl paragraphControl = new ParagraphControl();
      //paragraphControl.BeginUpdate();

      paragraphControl.PrecededBySameBorders = (previous != null) ? (paragraph.Properties.BordersEqual(previous.Properties)) : false;
      paragraphControl.FollowedBySameBorders = (next != null) ? (paragraph.Properties.BordersEqual(next.Properties)) : false;

      bool precededBySameStyle = (previous != null) ? (paragraph.Properties.StyleId == previous.Properties.StyleId) : false;
      bool followedBySameStyle = (next != null) ? (paragraph.Properties.StyleId == next.Properties.StyleId) : false;

      bool contextualSpacing = paragraph.Properties.ContextualSpacing ?? false;
      paragraphControl.SpacingBefore = (contextualSpacing && precededBySameStyle) ? 0 : paragraph.Properties.SpacingBefore.GetValueOrDefault().ToPixels();
      paragraphControl.SpacingAfter = (contextualSpacing && followedBySameStyle) ? 0 : paragraph.Properties.SpacingAfter.GetValueOrDefault().ToPixels();

      paragraphControl.IndentLeft = paragraph.Properties.IndentLeft.GetValueOrDefault().ToPixels();
      paragraphControl.IndentRight = paragraph.Properties.IndentRight.GetValueOrDefault().ToPixels();
      paragraphControl.IndentFirstLine = paragraph.Properties.IndentFirstLine.GetValueOrDefault().ToPixels();
      paragraphControl.IndentHanging = paragraph.Properties.IndentHanging.GetValueOrDefault().ToPixels();

      paragraphControl.BorderTop = FromDocumentBorder(paragraphControl,
        (paragraphControl.PrecededBySameBorders) ? paragraph.Properties.BorderBetween : paragraph.Properties.BorderTop);
      paragraphControl.BorderBottom = FromDocumentBorder(paragraphControl,
        (paragraphControl.FollowedBySameBorders) ? paragraph.Properties.BorderBetween : paragraph.Properties.BorderBottom);
      paragraphControl.BorderLeft = FromDocumentBorder(paragraphControl, paragraph.Properties.BorderLeft);
      paragraphControl.BorderRight = FromDocumentBorder(paragraphControl, paragraph.Properties.BorderRight);

      paragraphControl.DefaultTabStop = _documentModel.DefaultTabStop.ToPixels();
      paragraphControl.HorizontalAlignment = FromHorizontalAlignment(paragraph.Properties.Alignment ?? TextGlow.Control.Model.HorizontalAlignment.Left);

      paragraphControl.SpacingLine = paragraph.Properties.SpacingLine ?? Unit.Percent(1);

      paragraphControl.Background = FromShading(paragraph.Properties.Shading);

      IList<InlineControl> inlineControls = new List<InlineControl>();

      var tabStops = paragraph.Properties.TabStops.Select(t => new TextGlow.Control.Controls.Document.TabStop()
      {
        Type = (TextGlow.Control.Controls.Document.TabStopType)t.Type,
        Position = t.Position.ToPixels()
      });

      paragraphControl.TabStops.AddRange(tabStops);

      if (paragraph.Properties.NumberingLevel != null)
      {
        RunControl runControl = new RunControl();

        string listText = paragraph.Properties.NumberingLevel.Text;
        // HACK
        if (paragraph.Properties.NumberingLevel.NumberFormat == NumberFormat.Bullet
          && listText.Length == 1)
        {
          if (listText[0] == 61607)
            listText = "▪";
          else if (listText[0] > 10000)
            listText = "●";
        }
        else
        {
          listText = ReplaceListNumbers(listText, delegate(int level)
          {
            bool currentLevel = (level == paragraph.Properties.NumberingLevelNumber.Value);
            int count = GetNumberCount(paragraph.Properties.NumberingId.Value, level, currentLevel);
            return FormatNumber(count, paragraph.Properties.NumberingLevel.NumberFormat);
          });
        }

        runControl.Text = listText;
        runControl.FontFamily = GetFont(paragraph.Properties.NumberingLevel.RunProperties.Fonts.Ascii ?? paragraph.Properties.RunProperties.Fonts.Ascii);
        runControl.FontSize = (paragraph.Properties.NumberingLevel.RunProperties.FontSize ?? paragraph.Properties.RunProperties.FontSize ?? DefaultFontSize).ToPixels();
        if (paragraph.Properties.NumberingLevel.RunProperties.Bold ?? paragraph.Properties.RunProperties.Bold ?? false)
          runControl.FontWeight = FontWeights.Bold;
        if (paragraph.Properties.NumberingLevel.RunProperties.Italics ?? paragraph.Properties.RunProperties.Italics ?? false)
          runControl.FontStyle = FontStyles.Italic;
        runControl.Foreground = new SolidColorBrush(FromDocumentColor(paragraph.Properties.NumberingLevel.RunProperties.Color ?? paragraph.Properties.RunProperties.Color, DefaultTextColor) ?? DefaultTextColor);

        inlineControls.Add(runControl);

        TabControl tabControl = new TabControl();
        tabControl.Type = TextGlow.Control.Controls.Document.TabStopType.Num;
        inlineControls.Add(tabControl);
      }

      List<InlineContent> contents = paragraph.Content;
      inlineControls.AddRange(CreateInlineControls(ref contents));
      paragraph.Content = contents;

      // add blank line for empty paragraph
      if (inlineControls.IsEmpty())
      {
        inlineControls.Add(new RunControl()
        {
          FontSize = (paragraph.Properties.RunProperties.FontSize ?? DefaultFontSize).ToPixels(),
          FontFamily = GetFont(paragraph.Properties.RunProperties.Fonts.Ascii),
          Text = ""
        });
      }

      paragraphControl.Inlines.AddRange(inlineControls);

      return paragraphControl;
    }

    private List<InlineControl> CreateInlineControls(ref List<InlineContent> contents)
    {
      List<InlineControl> inlineControls = new List<InlineControl>();

      for (int i = 0; i < contents.Count; i++)
      {
        InlineContent content = contents[i];

        if (content is TextGlow.Control.Model.Run)
        {
          inlineControls.AddRange(BuildRuns((TextGlow.Control.Model.Run)content));
        }
        else if (content is TextGlow.Control.Model.Hyperlink)
        {
          TextGlow.Control.Model.Hyperlink hyperlink = (TextGlow.Control.Model.Hyperlink)content;

          HyperLinkControl hyperlinkControl = new HyperLinkControl();
          hyperlinkControl.Anchor = hyperlink.Anchor;
          hyperlinkControl.Uri = hyperlink.HyperlinkUri;

          for (int j = 0; j < hyperlink.Content.Count; j++)
          {
            TextGlow.Control.Model.Run run = (TextGlow.Control.Model.Run)hyperlink.Content[j];
            hyperlinkControl.Inlines.AddRange(BuildRuns(run));

            if (_pageEnded)
              run.Content = CollectionUtils.Slice(run.Content, j, null).ToList();
          }

          inlineControls.Add(hyperlinkControl);
        }
        else if (content is SmartTag)
        {
          SmartTag smartTag = (SmartTag)content;

          List<InlineContent> smartTagContents = smartTag.Content;
          inlineControls.AddRange(CreateInlineControls(ref smartTagContents));
          smartTag.Content = smartTagContents;
        }

        if (_pageEnded)
        {
          contents = CollectionUtils.Slice(contents, i, null).ToList();
          break;
        }
      }

      return inlineControls;
    }

    private string FormatNumber(int value, NumberFormat format)
    {
      switch (format)
      {
        case NumberFormat.Decimal:
          return value.ToString();
        case NumberFormat.CardinalText:
          return NumberUtils.ToWord(value);
        case NumberFormat.LowerLetter:
          return NumberUtils.ToLetter(value).ToLower();
        case NumberFormat.LowerRoman:
          return NumberUtils.ToRomanNumeral(value).ToLower();
        case NumberFormat.Ordinal:
          return NumberUtils.ToOrdinalWord(value);
        case NumberFormat.UpperLetter:
          return NumberUtils.ToLetter(value).ToUpper();
        case NumberFormat.UpperRoman:
          return NumberUtils.ToRomanNumeral(value).ToUpper();
        default:
          throw new ArgumentOutOfRangeException("format");
      }
    }

    private string ReplaceListNumbers(string text, Func<int, string> replacer)
    {
      StringBuilder sb = new StringBuilder();
      bool percentage = false;
      string number = string.Empty;

      foreach (char c in text)
      {
        if (percentage)
        {
          if (char.IsDigit(c))
          {
            number += c.ToString();
          }
          else
          {
            sb.Append(replacer(Convert.ToInt32(number) - 1));
            number = string.Empty;
            percentage = false;
            sb.Append(c);
          }
        }
        else
        {
          if (c == '%')
            percentage = true;
          else
            sb.Append(c);
        }
      }

      if (!string.IsNullOrEmpty(number))
        sb.Append(replacer(Convert.ToInt32(number)));

      return sb.ToString();
    }

    private IList<InlineControl> BuildRuns(TextGlow.Control.Model.Run run)
    {
      IList<InlineControl> runControls = new List<InlineControl>();

      for (int i = 0; i < run.Content.Count; i++)
      {
        RunContent runContent = run.Content[i];

        if (runContent is Text)
        {
          Text text = (Text)runContent;

          RunControl runControl = new RunControl();
          runControl.Text = text.Value;
          runControl.FontSize = run.Properties.FontSize.GetValueOrDefault(DefaultFontSize).ToPixels();
          runControl.Foreground = new SolidColorBrush(FromDocumentColor(run.Properties.Color, DefaultTextColor) ?? DefaultTextColor);
          if (run.Properties.Italics.GetValueOrDefault())
            runControl.FontStyle = FontStyles.Italic;
          if (run.Properties.Bold.GetValueOrDefault())
            runControl.FontWeight = FontWeights.Bold;
          runControl.FontFamily = GetFont(run.Properties.Fonts.Ascii);

          if (run.Properties.Underline != null)
            runControl.TextDecorations = TextDecorations.Underline;

          if (!string.IsNullOrEmpty(run.OpenDocumentStyleId))
          {
            string value;
            if (run.OpenDocumentStyle.TextProperties.TryGetValue("text-blinking", out value))
              runControl.Blink = Convert.ToBoolean(value);
          }

          runControls.Add(runControl);
        }
        else if (runContent is Tab)
        {
          TabControl tabControl = new TabControl();

          runControls.Add(tabControl);
        }
        else if (runContent is TextGlow.Control.Model.Image)
        {
          TextGlow.Control.Model.Image image = (TextGlow.Control.Model.Image)runContent;

          ImageInlineControl imageInlineControl = new ImageInlineControl();
          imageInlineControl.ImageUri = image.ImageUri;
          imageInlineControl.Width = image.Width.ToPixels();
          imageInlineControl.Height = image.Height.ToPixels();
          imageInlineControl.SetImageSource = delegate(System.Windows.Controls.Image ic)
          {
            using (Stream bitmapStream = _documentModel.Package.ContentGetter.GetContent(PackageUriHelper.GetDownloaderPartName(image.ImageUri)))
            {
              BitmapImage bitmapImage = new BitmapImage();
              bitmapImage.SetSource(bitmapStream);

              ic.Source = bitmapImage;
            }
          };

          runControls.Add(imageInlineControl);
        }
        else if (runContent is TextGlow.Control.Model.LineBreak)
        {
          TextGlow.Control.Model.LineBreak lineBreak = (TextGlow.Control.Model.LineBreak)runContent;

          switch (lineBreak.Type)
          {
            case LineBreakType.Line:
              runControls.Add(new LineBreakControl());
              break;
            case LineBreakType.Page:
              _pageEnded = true;
              break;
            case LineBreakType.Column:
            case LineBreakType.TextWrapping:
            default:
              throw new ArgumentOutOfRangeException("Type", "Unexpected line break type: " + lineBreak.Type);
          }
        }

        if (_pageEnded)
        {
          run.Content = CollectionUtils.Slice(run.Content, i + 1, null).ToList();
          break;
        }
      }

      return runControls;
    }
    #endregion

    #region Converters
    private Brush FromShading(Shading shading)
    {
      BackgroundPattern pattern = shading.Pattern ?? BackgroundPattern.Nil;

      switch (pattern)
      {
        case BackgroundPattern.Solid:
          return new SolidColorBrush(FromDocumentColor(shading.Color, DefaultBackgroundColor) ?? DefaultBackgroundColor);
        case BackgroundPattern.Nil:
        case BackgroundPattern.Clear:
          return new SolidColorBrush(FromDocumentColor(shading.FillColor, DefaultBackgroundColor) ?? DefaultBackgroundColor);
        default:
          return new SolidColorBrush(FromDocumentColor(shading.Color, DefaultBackgroundColor, GetOpacity(pattern)) ?? DefaultBackgroundColor);
      }
    }

    private double GetOpacity(BackgroundPattern pattern)
    {
      switch (pattern)
      {
        case BackgroundPattern.Pct5:
          return 0.05;
        case BackgroundPattern.Pct10:
          return 0.1;
        case BackgroundPattern.Pct12:
          return 0.15;
        case BackgroundPattern.Pct15:
          return 0.15;
        case BackgroundPattern.Pct20:
          return 0.2;
        case BackgroundPattern.Pct25:
          return 0.25;
        case BackgroundPattern.Pct30:
          return 0.3;
        case BackgroundPattern.Pct35:
          return 0.35;
        case BackgroundPattern.Pct37:
          return 0.37;
        case BackgroundPattern.Pct40:
          return 0.40;
        case BackgroundPattern.Pct45:
          return 0.45;
        case BackgroundPattern.Pct50:
          return 0.50;
        case BackgroundPattern.Pct55:
          return 0.55;
        case BackgroundPattern.Pct60:
          return 0.60;
        case BackgroundPattern.Pct62:
          return 0.62;
        case BackgroundPattern.Pct65:
          return 0.65;
        case BackgroundPattern.Pct70:
          return 0.70;
        case BackgroundPattern.Pct75:
          return 0.75;
        case BackgroundPattern.Pct80:
          return 0.8;
        case BackgroundPattern.Pct85:
          return 0.85;
        case BackgroundPattern.Pct87:
          return 0.87;
        case BackgroundPattern.Pct90:
          return 0.9;
        case BackgroundPattern.Pct95:
          return 0.95;
        default:
          return 1;
      }
    }

    private System.Windows.Media.FontFamily GetFont(string documentFont)
    {
      if (documentFont == null)
        throw new ArgumentNullException("documentFont");

      Font font = _documentModel.Fonts[documentFont];
      if (font == null)
        throw new ArgumentOutOfRangeException("documentFont", string.Format("Could not find font {0} in document fonts.", documentFont));

      string familyNamePlusFallback = font.Name + ", " + GetFamilyFallback(font.Family.Value);

      return new System.Windows.Media.FontFamily(familyNamePlusFallback);
      //return new FontFamily(documentFont ?? _documentModel.DefaultRunProperties.Fonts.Ascii);
    }

    private string GetFamilyFallback(TextGlow.Control.Model.FontFamily fontFamily)
    {
      switch (fontFamily)
      {
        case TextGlow.Control.Model.FontFamily.Auto:
        case TextGlow.Control.Model.FontFamily.Decorative:
        case TextGlow.Control.Model.FontFamily.Script:
          return "Arial";
        case TextGlow.Control.Model.FontFamily.Modern:
          return "Courier New";
        case TextGlow.Control.Model.FontFamily.Roman:
          return "Times New Roman";
        case TextGlow.Control.Model.FontFamily.Swiss:
          return "Arial";
        default:
          throw new ArgumentOutOfRangeException("fontFamily", string.Format("Unexpected font family: {0}", fontFamily));
      }
    }

    private static Color? FromDocumentColor(DocumentColor? color, Color autoDefault)
    {
      return FromDocumentColor(color, autoDefault, 1);
    }

    private static Color? FromDocumentColor(DocumentColor? color, Color autoDefault, double opacity)
    {
      if (color == null)
        return null;

      if (color.Value.Auto)
        return autoDefault;

      byte alpha = Convert.ToByte(opacity * 255);

      return Color.FromArgb(alpha, color.Value.R, color.Value.G, color.Value.B);
    }

    private static TextGlow.Control.Controls.Document.Border FromDocumentBorder(FrameworkElement parent, TextGlow.Control.Model.Border border)
    {
      TextGlow.Control.Controls.Document.Border newBorder = new TextGlow.Control.Controls.Document.Border()
      {
        Color = FromDocumentColor(border.Color, DefaultBorderColor) ?? DefaultBorderColor,
        Width = border.BorderWidth.GetValueOrDefault().ToPixels(),
        Spacing = border.Spacing.GetValueOrDefault().ToPixels(),
        Style = border.BorderStyle
      };

      return newBorder;
    }

    public static System.Windows.VerticalAlignment FromVerticalAlignment(TextGlow.Control.Model.VerticalAlignment verticalAlignment)
    {
      switch (verticalAlignment)
      {
        case TextGlow.Control.Model.VerticalAlignment.Bottom:
          return System.Windows.VerticalAlignment.Bottom;
        case TextGlow.Control.Model.VerticalAlignment.Center:
          return System.Windows.VerticalAlignment.Center;
        case TextGlow.Control.Model.VerticalAlignment.Top:
          return System.Windows.VerticalAlignment.Top;
        case TextGlow.Control.Model.VerticalAlignment.Both:
        default:
#if STRICT
          throw new ArgumentOutOfRangeException("verticalAlignment", string.Format("Unexpected vertical alignment: {0}", verticalAlignment));
#else
          return System.Windows.VerticalAlignment.Top;
#endif
      }
    }

    public static System.Windows.HorizontalAlignment FromHorizontalAlignment(TextGlow.Control.Model.HorizontalAlignment horizontalAlignment)
    {
      switch (horizontalAlignment)
      {
        case TextGlow.Control.Model.HorizontalAlignment.Right:
          return System.Windows.HorizontalAlignment.Right;
        case TextGlow.Control.Model.HorizontalAlignment.Center:
          return System.Windows.HorizontalAlignment.Center;
        case TextGlow.Control.Model.HorizontalAlignment.Left:
          return System.Windows.HorizontalAlignment.Left;
        case TextGlow.Control.Model.HorizontalAlignment.Both:
        default:
#if STRICT
          throw new ArgumentOutOfRangeException("horizontalAlignment", string.Format("Unexpected horizontal alignment: {0}", horizontalAlignment));
#else
          return System.Windows.HorizontalAlignment.Left;
#endif
      }
    }
    #endregion
  }
}